import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A small program for remembering filenames / strings passed to it.
 * This program was inspired by the popd/pushd commands that are built
 * into Bash. This program however, lets you access the saved files
 * in the manner of an array rather than a stack while also allowing
 * you to save strings that are not restricted to being representations
 * of files.
 * @author Fletcher Johnson
 * 
 */
public class Remember {
	
	/** Usage information. */
	private static final String usage = "rr [-a files] | [#item] | -r | -c | -h";
	
	/** Default name of the file where item history is stored. */
	private static final String HFILE = ".rrhistory";
	
	/** The default file location where the history is saved.*/
	private static File DEFAULT_SAVE = new File (
			System.getProperty("user.home") + File.separator + HFILE);
	
	/** A file pointing to the user's home directory. */
	private static File parent = new File(System.getProperty("user.home"));
	
	/** Singleton instance of this class. */
	private static final Remember rmb = new Remember();
	
	/**If this variable is true then when the user asks for an item do not
	automatically remove it from the history. */
	private static boolean KEEP_ALIVE = false;
	
	/** The help message. */
	private static final String HELP = "rr [options] | #item \n" + 
		"Options: \n" +
		"Omitting options lists the saved items\n" +
		"-a file1 file2 ... : Add items to the history\n" +
		"-r #item : Access item at the specified index and remove" +
		" it from the history\n" +
		"-c : Clear the history\n" +
		"-h : print usage message\n" +
		"--help : This help message\n";
	
	private Remember(){}
	
	/**
	 * Get the singleton instance object of this class.
	 * @return The singleton object.
	 */
	public static Remember getInstance(){ return rmb; }
	
	public static void main(String args[]) throws IOException{
		
		if(args.length == 0){
			rmb.printSaved(rmb.listSaved());
			System.exit(0);
		}
		
		if(args[0].equals("--help")){
			System.out.println(HELP);
			System.exit(0);
		}
		
		if(args.length == 1){
			if(args[0].equals("-h") ){
				System.out.println(HELP);
				System.exit(0); 
			}else if(args[0].equals("-c")){
				System.exit(rmb.clearItems() ? 0 : 1);
			}else{
				KEEP_ALIVE = true;
				System.exit(rmb.printItem(args[0]) ? 0 : 1);
			}
		}
		
		if(args.length == 2 && args[0].equals("-r")){
			KEEP_ALIVE = false;
			System.exit(rmb.printItem(args[1]) ? 0 : 1);
		}
		
		if(args.length >= 2){
			boolean forceCheck = true;
			
			if(args[0].equals("-a")){
				forceCheck = false;
			}else if(args[0].equals("-f")){
				forceCheck = true;
			}else{
				System.out.println(usage);
				System.exit(1);
			}
			
			for(int i = 1; i < args.length ; i++){
				rmb.addItem(args[i],forceCheck);
			}
			
			System.exit(0); //success
		}
		
		System.out.println(usage);
		System.exit(1);
		
	}
	
	/**
	 * Retrieve a saved item at the index specified by the String 'arg'.
	 * @return True if the desired item was output to stdout successfully.
	 * Also true if the item specified does not exist (the index 
	 * specified was bad). 
	 * @throws IOException 
	 */
	private boolean printItem(String arg) throws IOException{
		
		try{
			int index = Integer.parseInt(arg);
			String result = rmb.readSaved(index);
			
			if(result != null){
				System.out.print(result);
			}
			
			return true;
			
		}catch(NumberFormatException e){
			System.out.println(usage);
			return false;
		}
	}
	
	/**
	 * Add an item to the history.
	 * Default access due to Junit testing.
	 * @param item The item to be memorised.
	 * @param forceCheck Store the absolute path to the passed in item 
	 * if this variable is true.
	 * @return False if an error occured.
	 * @throws IOException
	 */
	boolean addItem(String item, boolean forceCheck) throws IOException{
		File saveLocation = DEFAULT_SAVE;
		FileWriter writer = null;
		BufferedWriter bufwriter = null;
		
		if(forceCheck){
			item = new File(item).getAbsolutePath();
			//item = escape(item);
		}
		
		if(saveLocation.exists()){
			if(saveLocation.canWrite()){
				writer = new FileWriter(saveLocation,true);
				bufwriter = new BufferedWriter(writer);
				
				try {
					bufwriter.append(item.trim() + '\n');
				} catch (IOException e) {
					try{
						bufwriter.close();
					}catch(IOException ex){
						throw e; //report original error;
					}
					throw e;
				} 
				bufwriter.close();
				return true;
			}else{
				displayErr();
				return false;
			}
		}else{
			if(parent.canWrite()){
				
				writer = new FileWriter(saveLocation,true);
				bufwriter = new BufferedWriter(writer);
				
				try{
					bufwriter.write(item.trim() + '\n');
				}
				catch(IOException e){
					try{
						bufwriter.close();
					}catch(IOException ex){
						throw e;
					}
					throw e;
				}
				bufwriter.close();
				return true;
			}else{
				displayErr();
				return false;
			}
		}
	}
	
	String escape(String item){
		int itemlen = item.length();
		StringBuilder escaped = new StringBuilder(itemlen);
	
		//Linux metachars
		//http://www.linux.org/docs/ldp/howto/
		//Secure-Programs-HOWTO/handle-metacharacters.html
		char[] metachars = new char[]{'&', ';', '`', '\'','\\', 
			'"', '|', '*', '?', '~', '<', '>', '^', '(', ')',
			'[', ']', '{', '}', '$', '\n', '\r', ' ', '\t'};
		
		Arrays.sort(metachars);
			
		for(int i = 0; i < itemlen; i++){
			char c = item.charAt(i);
			if(Arrays.binarySearch(metachars, c) >= 0){
				escaped.append('\\');
			}
			escaped.append(c);		
		}
		return escaped.toString();
	}
	
	/**
	 * Clears the history.
	 * @return True if the history was cleared successfully.
	 */
	boolean clearItems(){
		if(!DEFAULT_SAVE.exists()) return true;
		
		return DEFAULT_SAVE.delete();
	}
	
	/**
	 * Return the item stored at the specified index.
	 * Default access due to Junit testing.
	 * @param index item at index number, index.
	 * @return null if the item at index does not exist, the item otherwise.
	 * @throws IOException
	 */
	String readSaved(int index) throws IOException{
		if(!DEFAULT_SAVE.exists()){
			return null;
		}
		
		File tempFile = new File(System.getProperty("user.home") + 
				File.separator + ".rrhistorytmp");
		
		FileReader freader = new FileReader(DEFAULT_SAVE);
		BufferedReader bufread = new BufferedReader(freader);
		
		FileWriter tempWriter = new FileWriter(tempFile,true);
		BufferedWriter buftWriter = new BufferedWriter(tempWriter);
		
		String retval = null;
		String item;
		int i = 0;
		
		while( (item = bufread.readLine()) != null){
			try{
				if(i == index){
					retval = item;
					
					//Do not remove this item from the history.
					if(KEEP_ALIVE){
						buftWriter.write(item + '\n');
					}
				}
				else{
					buftWriter.write(item + '\n');
				}
				i++;
			}catch(IOException e){
				buftWriter.close();
				throw e;
			}
		}
		
		bufread.close();
		buftWriter.close();
		
		//copy and overwrite old history
		if(!tempFile.renameTo(DEFAULT_SAVE)){
			throw new IOException("Could not update history");
		}
		
		return retval;
	}
	
	/**
	 * Returns a list of the saved items.
	 * Default access due to Junit testing.
	 * @return a list of the saved items, null if no items.
	 * @throws IOException
	 */
	List<String> listSaved() throws IOException{
		if(!DEFAULT_SAVE.exists()){
			return null; //no saved items.
		}
		
		FileReader reader = new FileReader(DEFAULT_SAVE);
		BufferedReader bufreader = new BufferedReader(reader);
		List<String> files = null;
		
		
		String item;
		try{
			while((item = bufreader.readLine()) != null){
				if(files == null){
					files = new ArrayList<String>();
				}
				files.add(item);
			}
		}catch(IOException e){
			try{
				bufreader.close();
			}catch(IOException ex){
				throw e; //throw original message.
			}
			throw e; 
		}
		
		bufreader.close();
		return files;
	}
	
	/**
	 * Print out saved item history to stdout.
	 * @param files Saved files, null if no items. 
	 */
	private void printSaved(List<String> files){
		if(files == null){
			return;
		}
		
		int i = 0;
		for(String file : files){
			System.out.println("["+i+"] " +file);
			i++;
		}
	}
	
	/**
	 * Display an error when writing to the file that contains the history
	 * generated an error.
	 */
	private static void displayErr(){
		System.err.println("Error writing to file that " +
				"contains saved history: " 
				+ System.getProperty("user.home") + HFILE);
	}
}
